/**
 * @typedef Envelope
 * @description Format of the messages received from the Pilot Socket Server
 * @type {object} 
 * @property {object} data
 * @property {object|object[]} data.payload - The message sent for your request.
 * @property {boolean} marker - Is this message a marker message for queries?
 * @property {boolean} endMarker - Is this message an end marker message (only applicable if marker == true)
 * @property {string} requestId - Request ID that this message applies to 
 * @property {string} errorCode - Error Code is populated if an error occurred
 * @property {string[]} errorArguments - Arguments to resolve the formatted message (source of formatting TBD)
 */


/**
 * @typedef ErrorCallback
 * @type {function}
 * @param {Envelope} message - The Raw Message in Error
 */

/**
 * @typedef MessageCallback
 * @type {function}
 * @param {object} object - The object presented by the request (incrementally [one by one])
 */

/**
 * @typedef QueryStartCallback
 * @type {function}
 * @param {object} startMarker - Query metadata
 */

/**
 * @typedef QueryEndCallback
 * @type {function}
 * @param {object} endMarker - Query metadata
 */

/**
 * @typedef MessageListener
 * @type {object}
 * @mixin
 * 
 * @property {MessageCallback} onMessage - Incremental Data returned by the request 
 * @property {ErrorCallback} [onError] - Errors Associated with the current request
 * <p>If this callback is not defined, the error will be reported to the PilotCommunicator#onError</p>
 */

/**
 * @typedef QueryMessageListener
 * @mixes MessageListener
 * @type {object}
 * 
 * @property {QueryStartCallback} onStart - For query based requests, this will be called when the start marker is received
 * @property {QueryEndCallback} onEnd - For query based requests, this will be called when the end marker is received
 * <p><b><u>*For queries with no data only this callback will be invoked*</u></b></p>
 */

/**
 * @typedef PilotConfigurationOptions
 * @type {object}
 * @property {function} onConnect - Called when connected to Pilot
 * @property {function} onDisconnect - Called when disconnected from Pilot
 * @property {function} onConnectFail - Called when a connection fails
 */


/**
 * @typedef PilotRequest
 * @type {object}
 * @property {string} requestId - Client generated request id, automatically assigned if not provided
 */

/**
 * @typedef BusinessObject
 * @type {object}
 * @property {int} classId - The class id as assigned by pilot
 */


/**
 * Pilot Communication Class
 * @class
 * @param {PilotConfigurationOptions} options - Options for this pilot connection
 */
export function PilotCommunicator(options) {
    this._logger = console;
    var self = this;
    this._MAX_RECONNECT_TIMEOUT = 30000;
    this._webSocket = null;
    this._version = "0.9.0";
    this._callbackConnect = [];
    this._callbackDisconnect = [];
    this._callbackError = [];
    this._callbackFail = [];
    this._jsonParserCallback = null;
    this.currentUser = null;

    window.addEventListener('unload', function() {
        self._logger.info("Cleaning up PilotAPI");
        this.disconnect();
    }.bind(this));

    this.isConnected = false;
    this._requestListenerMap = [];
    this._subscriptionMap = [];

    if (typeof options == "object" && options != null) {
        self._copyCallback(options, "onError", this._callbackError);
        self._copyCallback(options, "onConnect", this._callbackConnect);
        self._copyCallback(options, "onDisconnect", this._callbackDisconnect);
        self._copyCallback(options, "onConnectFail", this._callbackFail);
    }
    if (typeof options.jsonParseCallback == "function") {
        this._jsonParserCallback = options.jsonParseCallback;
    }

    this._reconnectTimeout = 2000;
    this._retryCount = 0;
    this.autoReconnect = true;
    if (options != null && options.autoReconnect != null && typeof options.autoReconnect == "boolean") {
        this.autoReconnect = options.autoReconnect;
    }
}

PilotCommunicator.ErrorMissingCommunicator = "PilotCommunicator is required on the session.";

PilotCommunicator.saveToSession = function(url, token) {
    sessionStorage.setItem("_pilotToken", token);
    sessionStorage.setItem("_pilotURL", url);
}

PilotCommunicator.loadFromSession = function(options) {
    var url = sessionStorage.getItem("_pilotURL");
    var token = sessionStorage.getItem("_pilotToken");
    if (typeof url != "string" || url == null) {
        throw new Error(PilotCommunicator.ErrorMissingCommunicator);
    }
    if (typeof token != "string" || token == null) {
        throw new Error(PilotCommunicator.ErrorMissingCommunicator);
    }
    var communicator = new PilotCommunicator(options);
    communicator.connect(url, token);
    return communicator;
}


PilotCommunicator.prototype._isFunction = function(target) {
        return typeof target == "function";
    }
    /**
     * Copies Function from the object to target
     * @ignore
     */
PilotCommunicator.prototype._copyCallback = function(object, key, collection) {
    if (typeof object[key] == "function") {
        this._addCallback(collection, object[key]);
    }
}

/**
 * @ignore
 */
PilotCommunicator.prototype._addCallback = function(collection, callback) {
    if (Array.isArray(collection) == false) {
        this._logger.error("Callback attached to a non Array");
        return;
    }
    if (typeof callback != "function") {
        this._logger.error("Callback is not a function");
        return;
    }
    collection.push(callback);
}

PilotCommunicator.prototype._executeCallbacks = function(collection, parameters) {
    var self = this;
    if (Array.isArray(collection)) {
        collection.forEach(function(o) {
            o.call(self, parameters);
        });
    }
}

/**
 * Event Handler: Connected to Pilot
 * @ignore
 */
PilotCommunicator.prototype.onConnect = function() {
    this.isConnected = true;
    this._executeCallbacks(this._callbackConnect, arguments);
}

PilotCommunicator.prototype.addConnectCallback = function(callback) {
    this._addCallback(this._callbackConnect, callback);
}

/**
 * Event Handler: Disconnected From Pilot
 * @ignore 
 */
PilotCommunicator.prototype.onDisconnect = function() {
    this._executeCallbacks(this._callbackDisconnect, arguments);
}

/**
 * Event Handler: Pilot Connection Failure or server unavailable
 * @ignore
 */
PilotCommunicator.prototype.onConnectFail = function(e) {
    this._executeCallbacks(this._callbackFail, arguments);
}


/**
 * High Level Error function, individual calls should return errors on promises
 * @ignore
 */
PilotCommunicator.prototype.onError = function(e) {
    this._executeCallbacks(this._callbackError, arguments);
}

/**
 * Connecting to pilot via WebSocket and provide callback to the listener for connection events and data received from server side
 * @see {@link login}
 * @param {string} urls - The urls of the socket server or F5 to connect to
 * @param {string} token - The assertion token need to validate identity of client the result of login
 */
PilotCommunicator.prototype.connect = function(urls, token, isReconnect) {
    var self = this;
    if (typeof this._webSocket == "undefined" || this._webSocket == null || this._webSocket.readyState == WebSocket.CLOSED) {
        if (urls != null && urls.length > 0) {
            var url = urls;
            self.url = url;
            self.token = token;
        }

        if (token == null) {
            self.onError("Invalid Token");
            self._logger.warn("Invalid Token");
            return;
        }

        var encodedURI = url + "?token=" + encodeURIComponent(token);


        this._webSocket = new WebSocket(encodedURI);
        this._webSocket.onopen = function(event) {
            self._logger.info("Socket opened", urls);
            event.isReconnect = isReconnect;
            self.onConnect(event);
            if (isReconnect) {
                self._retryCount = 0;
                self._reconnectTimeout = 2000;
                self._reIssueQNS();
            }
        };
        this._webSocket.onclose = function(event) {
            self._logger.debug("Socket closed");
            self.onDisconnect();
            self.isConnected = false;
            if (!event.wasClean && self.autoReconnect) {
                self._reconnect(urls, token);
            }
        };
        this._webSocket.onerror = function(event) {
            self._logger.log("Socket error");
            self.onConnectFail(event);
        };
        this._webSocket.onmessage = this._dispatchMessage.bind(this);
    } else {
        self._logger.debug("Already connected to pilot");
    }
}

/**
 * @ignore
 */
PilotCommunicator.prototype._reconnect = function(urls, token) {
    var self = this;
    if (this._retryCount == 0) {
        // Adding a cool down period on before reconnecting after disconnect
        var coolDownTime = PilotUtils.randomIntFromInterval(1, 10) * 1000;
        self._logger.info("Cool down ... Waiting for " + coolDownTime / 1000 + " seconds before attempting to reconnect");
        setTimeout(this.connect.bind(this), coolDownTime, urls, token, true);
        this._retryCount++;
    } else {
        self._logger.info("Reconnecting ... retry # " + this._retryCount + " in " + this._reconnectTimeout / 1000 + " seconds");
        setTimeout(this.connect.bind(this), this._reconnectTimeout, urls, token, true);
        this._retryCount++;
        if (this._reconnectTimeout < this._MAX_RECONNECT_TIMEOUT) {
            this._reconnectTimeout *= 2;
        } else {
            this._reconnectTimeout = 2000;
        }
    }
}
PilotCommunicator.prototype.reconnect = function() {
        var self = this;
        self._reconnect(self.url, self.token);
    }
    /**
     * If no request ids are generated, create a pseudo random uuid for that user (not globally shared on the server)
     * @ignore
     */
PilotCommunicator.prototype._newUUID = function() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0,
            v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); });
}

/**
 * @ignore
 */
PilotCommunicator.prototype._getInterestedListeners = function(requestId) {
    var listeners = [];

    if (typeof this._requestListenerMap[requestId] != "undefined") {
        listeners.push(this._requestListenerMap[requestId]);
    }
    return listeners;
}

/**
 * @ignore
 */
PilotCommunicator.prototype._callIfExists = function(targetObject, fnName, args) {
    if (typeof targetObject[fnName] == "function") {
        targetObject[fnName].apply(targetObject, args);
        return true;
    }
    return false;
}

PilotCommunicator.prototype._parseJSON = function(json) {
    if (this._jsonParserCallback == null) {
        return JSON.parse(json);
    } else {
        return this._jsonParserCallback.apply(this, json);
    }
}

/**
 * @ignore
 */
PilotCommunicator.prototype._dispatchMessage = function(event) {
    var self = this;
    var response = event.data;
    var object = response;
    if (object == "Connection established") {
        return;
    }

    if (typeof object == "string") {
        try {
            object = self._parseJSON(object);
        } catch (e) {
            self._logger.error("Error Parsing: ", object);
            return;
        }
    }
    if (typeof object["data"] != "undefined" && object.data != null) {
        var data = object.data;
        var listeners = this._getInterestedListeners(data.requestId);
        var isMarker = data.marker;
        var payload = data.payload;
        if (typeof isMarker == 'boolean' && isMarker == true) {
            if (data.endMarker == false) {
                listeners.forEach(function(wrapper) {
                    self._callIfExists(wrapper.listener, "onStart", [object]);
                });

            } else {
                listeners.forEach(function(wrapper) {
                    if (wrapper.query) {
                        //this query only listener is done..clean it up
                        this._closeRequest({ "requestId": wrapper.requestId });
                    }

                    self._callIfExists(wrapper.listener, "onEnd", [object]);

                }.bind(this));
            }
            return;
        }
        if (typeof object["errorCode"] == "string" && object.errorCode != "0") {
            listeners.forEach(function(wrapper) {
                if (!self._callIfExists(wrapper.listener, "onError", [object])) {
                    self._logger.error("Error Received and not handled", object);
                }
            });
            return;
        }

        listeners.forEach(function(wrapper) {
            if (data.command == "ProxyInvoke") {
                wrapper.listener.onMessage(data.result);
            } else {
                wrapper.listener.onMessage(payload);
            }

        }.bind(this));
    } else {
        self._logger.error("Response contains no data: ", response);
    }
}


/**
 * Throws an exception as it is a development error
 * @ignore
 */
PilotCommunicator.prototype._validateQueryListener = function(listener) {
    if (typeof listener == "object") {
        if (typeof listener.onEnd == "undefined" || listener.onEnd == null) {
            throw new Error("function 'onEnd' is required for query listeners.");
        }
        if (typeof listener.onStart == "undefined" || listener.onStart == null) {
            throw new Error("function 'onStart' is required for query listeners.");
        }


    }
}

/**
 * Makes a query call to server to get data. Client will get an error call back if connection isn't open to the server or sql is not set or invalid.
 * @param {object} request Client need to construct a request object which contains query and client requestId to be sent to the server side. 
 * @param {QueryMessageListener} QueryMessageListener used for this query request.  
 */
PilotCommunicator.prototype.query = function(request, localListener) {
    var self = this;
    self._logger.debug("Query", request);
    self._validateQueryListener(localListener);
    return new Promise(function(resolve, reject) {
        if (!self._validateSQL(request, localListener)) {
            reject("invalid sql");
        } else {
            request.command = "query";
            self._recordListener(request, localListener);
            self._sendObject(request, true)
                .then(function() {
                    resolve(request.requestId);
                }, function(e) {
                    reject(e);
                });
        }
    });
}

PilotCommunicator.prototype.isSocketConnected = function() {
    return (typeof this._webSocket != "undefined" && this._webSocket != null && this._webSocket.readyState == WebSocket.OPEN);
}

/**
 * @ignore
 */
PilotCommunicator.prototype._validateSQL = function(request, localListener) {
    //DISABLING FOR NOW, PILOTSQL is not standard, let the server do it. 
    // if (!this.isValidSQL(request.query)){
    //   var errorListener = typeof localListener == "undefined" || localListener == null || localListener.onError ==null || typeof localListener.onError == "undefined"? this : localListener;
    //   errorListener.onError("Invalid SQL Statement");
    //   return false;
    // }  
    return true;
}

/**
 * 
 */
PilotCommunicator.prototype.subscribe = function(request, localListener) {
    this._logger.debug("Subscribe", request);
    if (!this._validateSQL(request, localListener)) {
        return;
    }
    request.command = "subscribe";
    this._recordListener(request, localListener);
    return this._sendObject(request, true);
}

/**
 * Makes a qns call to server. Client will get an error call back if connection isn't open to the server or sql is not set or invalid.
 * @param {PQNSContext} request - The Query Based Request
 * @param {QueryMessageListener} QueryMessageListener used for this query request.  
 * 
 */
PilotCommunicator.prototype.qns = function(request, localListener) {
    this._logger.debug("Query and Subscribe", request);
    this._validateQueryListener(localListener);
    if (!this._validateSQL(request, localListener)) {
        return new Promise(function(resolve, reject) {
            reject("Invalid SQL");
        });
    }


    request.command = "qns";
    this._recordListener(request, localListener);
    return this._sendObject(request, true);
}

/**
 * @ignore
 */
PilotCommunicator.prototype._recordListener = function(request, listener) {
    var self = this;
    if (typeof listener == "undefined" || listener == null) {
        throw new Error("Missing Listener");
    }
    if (typeof request.requestId == "undefined" || request.requestId == null) {
        request.requestId = this._newUUID();
    }
    var command = request.command;

    if ($.isFunction(listener.onMessage) == false) {
        throw new Error("onMessage callback missing");
    }

    if (typeof listener.onEnd != "function" && (command == "query" || command == "qns")) {
        self._logger.warn("**********************************************************");
        self._logger.warn("Without onEnd you will not know if an empty query is sent. Request: ", request);
        self._logger.warn("**********************************************************");
    }
    var isQueryOnly = command == "query";
    if (typeof listener != "undefined" && typeof request.requestId != "undefined") {
        var wrapper = new _MsgListenerWrapper(request.requestId, listener, isQueryOnly);
        this._requestListenerMap[request.requestId] = wrapper;
    }

    if (this.autoReconnect && !isQueryOnly) {
        this._subscriptionMap[request.requestId] = request;
    }
}

/**
 * @ignore
 */
PilotCommunicator.prototype._reIssueQNS = function() {
    for (var key in this._subscriptionMap) {
        this._sendObject(this._subscriptionMap[key], true);
    }
}

/**
 * @ignore
 */
PilotCommunicator.prototype._closeRequest = function(request) {
    if (typeof request.requestId != "undefined") {
        delete this._requestListenerMap[request.requestId];
        delete this._subscriptionMap[request.requestId];
    }
}

/**
 * @ignore
 */
PilotCommunicator.prototype._clearRequests = function() {
    this._requestListenerMap = [];
    this._subscriptionMap = [];
}

/**
 * Cancels an existing subscription based on the client requestId that client provides in the request object
 * @param {string} requestId - Particular Request to close the subscription
 */
PilotCommunicator.prototype.cancelSubscription = function(requestId) {
    var requestObject = {
        "command": "unsubscribe",
        "requestId": requestId,
    };

    this._closeRequest(requestObject);
    this._cancelSubscription(requestObject);
}

/**
 * @ignore
 */
PilotCommunicator.prototype._cancelSubscription = function(request) {
    this._logger.debug("Cancel subscription", request);
    request.command = "unsubscribe";
    this._sendObject(request, false);
    this._closeRequest(request);
}


/**
 * Cancels all existing subscriptions from a this client
 */
PilotCommunicator.prototype.cancelAllSubscriptions = function() {
    this._logger.debug("Cancel all subscription");
    var request = { command: "unsubscribe" };
    this._sendObject(request, false);
    this._clearRequests();
}

/**
 * Disconnect from Pilot
 */
PilotCommunicator.prototype.disconnect = function() {
    this._logger.log("Disconnect");
    if (this.isSocketConnected()) {
        this.cancelAllSubscriptions();
        this._webSocket.close();
        this._clearRequests();
    } else if (typeof this.onError == "function") {
        this.onError("Not Connected");
    }
}

/**
 * Before allowing to connect to pilot, user need to authenticated and obtain an assertion from AuthnService, and pass the assertion as token to connect to pilot.
 * @param {url} Pilot Authentication End Point  "https://pilot-dev.paic.com.cn/fleet/authentication"
 * @param {user} User to authenticated
 */
PilotCommunicator.prototype.login = function(url, user, password) {
    var deferred = $.Deferred();
    var self = this;
    var data = JSON.stringify({
        "username": user,
        "password": password,
        "authnType": "um",
        "lifeTime": "150000"
    });

    var xhr = new XMLHttpRequest();

    xhr.addEventListener("readystatechange", function() {
        if (this.readyState === 4) {
            if (this.status === 200 && this.responseText !== null && this.responseText !== "") {
                var response = JSON.parse(this.responseText);
                deferred.resolve(response);
                self.currentUser = user;
            } else {
                deferred.reject(this.status, this.responseText);
            }
        }
    });

    xhr.open("POST", url, true);
    xhr.setRequestHeader("content-type", "application/json");
    xhr.send(data);

    return deferred.promise();
}

/**
 * @ignore
 */
PilotCommunicator.prototype.isValidSQL = function(sql) {
    var self = this;
    try {
        SQLParser.parse(sql);
    } catch (err) {
        self._logger.error(err);
        return false;
    }
    return true;
}

/**
 * @ignore
 */
PilotCommunicator.prototype._sendObject = function(object, waitForAck) {
    /**
      if ( typeof waitForAck != "boolean") {
        waitForAck = false;
      }
    */
    //we dont support the proper promise from serverside, so disabling here for now
    waitForAck = false;

    var self = this;
    var promise = new Promise(function(resolve, reject) {
        if (!self.isSocketConnected()) {
            reject(Error("Not Connected"));
        } else {
            var clone = JSON.parse(JSON.stringify(object));
            object.waitForAck = waitForAck;

            self._webSocket.send(JSON.stringify(object));
            if (!waitForAck) {
                resolve(object);
            } //have to record and do this elsewhere
        }
    });

    return promise;

}

/**
 * @ignore
 */
PilotCommunicator.prototype._defaultRequest = function(request) {
    if (typeof request == "undefined" || request == null) {
        return request = {
            requestId: null,
        };
    } else {
        return request;
    }
}

/**
 * Retrieves the registered Pilot Proxies
 * @param {PilotRequest} [request] - The Request
 * @param {MessageListener} listener - The listener to accept the results
 */
PilotCommunicator.prototype.getProxyList = function(request, listener) {
    request = this._defaultRequest(request);
    this._logger.debug("Get Proxy List", request);
    request.command = "ProxyList";
    this._recordListener(request, listener);
    return this._sendObject(request, true);
}

/**
 * Retrieves the known Pilot Business Object Metadata
 * @param {PilotRequest} [request] - The Request
 * @param {MessageListener} listener - The listener to accept the results
 */
PilotCommunicator.prototype.getBusinessObjectsList = function(request, listener) {
    request = this._defaultRequest(request);
    this._logger.debug("Get MsgDef List", request);
    request.command = "MsgDefList";
    this._recordListener(request, listener);
    return this._sendObject(request, true);
}

/**
 * Calls the requested Proxy Method
 * @see PilotCommunicator#getProxyList
 * @param {PProxyRequest} request - The Proxy Call Request
 * @param {MessageListener} listener - The listener to accept the results
 */
PilotCommunicator.prototype.invokeProxy = function(request, listener) {
    request = this._defaultRequest(request);
    this._logger.debug("Invoke Proxy Method", request);
    request.command = "ProxyInvoke";
    this._recordListener(request, listener);
    return this._sendObject(request, true);
}

/**
 * Publish the requested object
 * @param {BusinessObject} object Business Object to Publish
 * @param {string} [msgDef] - The message definition to publish on, only required for business objects with multiple
 * message definitions without a default
 */
PilotCommunicator.prototype.publish = function(object, msgDef) {
    var self = this;
    var envelope = {
        "command": "Publish",
        payload: {
            "classId": object.classId,
            "payload": JSON.stringify(object),
        },
        "requestId": this._newUUID(),
    };

    if (typeof msgDef == "string") {
        envelope.payload.messageDefinition = msgDef;
    }

    var self = this;
    var promise = new Promise(function(resolve, reject) {
        if (typeof object == "undefined" || object == null) {
            reject("Object is not valued");
        }
        if (typeof object.classId == "undefined" || object.classId == null) {
            reject("Class Id for the object is required");
        }

        self._sendObject(envelope, true).then(function(o) {
            console.log("good received");
            console.log(o);
            resolve(o);
        }).catch(function(r) {
            console.log("failed" + r);
            reject(r);
        });
    });
    return promise;
}

/**
 * @class
 * @ignore
 */
function _MsgListenerWrapper(requestId, listener, isQuery) {
    this.requestId = requestId;
    this.listener = listener;
    this.query = isQuery;

    this.onError = function(e) {
        if (this.listener != null) {
            this.listener.onError(e);
        }
    }
}

/**
 * QNS Context for configuring requests for data
 * @class
 * @property {string} sql - Query for request
 * @property {string} [requestId] - The Client Assigned Request Id 
 */
export function PQNSContext(sql, id) {
    this.command;
    this.query = sql;
    this.requestId = id;
}


/**
 * Proxy Request Class used to invoke a Proxy Method
 * Metadata is provided in PilotCommunicator.getProxyList
 * @see {@link PilotCommunicator#getProxyList}
 * @class
 * @param {string} [id] - Client Request ID
 * @param {string} className - Proxy Class name from getProxyList
 * @param {string} methodName - Proxy Method to Invoke
 * @param {any[]} params - Proxy Parameters as Described by getProxyList
 */
export function PProxyRequest(id, className, methodName, params) {
    this.command;
    this.requestId = id;
    this.className = className;
    this.methodName = methodName;
    this.arguments = params;
}


function _PilotUtils() {
    this.clone = function(object) {
        if (object == null) return object;
        return JSON.parse(JSON.stringify(object));
    }

    this.randomIntFromInterval = function(min, max) {
        return Math.floor(Math.random() * (max - min + 1) + min);
    }

    this.isValued = function(o) {
        return typeof o != "undefined" && o != null;
    }

    this.isUnvalued = function(o) {
        return !this.isValued(o);
    }

}

function _PilotMarketDataUtils() {
    this.isSideBid = function(side) {
        return side == "BUY" || side == "BID" || side == "PLEDGE" || side == "PAY";
    };

    this.isSideAsk = function(side) {
        return side == "SELL" || side == "OFFER" || side == "UNPLEDGE" || side == "RECEIVE";
    };

    this.getSize = function(marketData) {
        if (marketData == null || marketData.side == null) {
            return 0;
        }
        return this.isSideAsk(marketData.side) ? marketData.bidSize : marketData.offerSize;
    };

    this.getPrice = function(marketData) {
        return this.isSideAsk(marketData.side) ? marketData.bidPrice : marketData.offerPrice;
    };

}


Date.prototype.format = function(format) //author: meizz
    {
        var o = {
            "M+": this.getMonth() + 1, //month
            "d+": this.getDate(), //day
            "h+": this.getHours(), //hour
            "m+": this.getMinutes(), //minute
            "s+": this.getSeconds(), //second
            "q+": Math.floor((this.getMonth() + 3) / 3), //quarter
            "S": this.getMilliseconds() //millisecond
        }

        if (/(y+)/.test(format)) format = format.replace(RegExp.$1,
            (this.getFullYear() + "").substr(4 - RegExp.$1.length));
        for (var k in o)
            if (new RegExp("(" + k + ")").test(format))
                format = format.replace(RegExp.$1,
                    RegExp.$1.length == 1 ? o[k] :
                    ("00" + o[k]).substr(("" + o[k]).length));
        return format;
    }


function _PilotSecurityUtils() {
    this.isStringEmptyOrNull = function(str) {
        return str == null || str.trim().length == 0;
    };

    this.coalesceStrings = function() {
        for (var i = 0; i < arguments.length; i++) {
            var currentValue = arguments[i];
            if (!this.isStringEmptyOrNull(currentValue)) {
                return currentValue;
            }
        }
        return null;
    }

    this.getShortName = function(security) {
        return this.coalesceStrings(security.security_short_name, security.sec_cn_sname, this.getFullName(security));
    }

    this.getFullName = function(security) {
        return this.coalesceStrings(security.security_full_name, security.sec_full_name, "");
    }

    this.calculateSize = function(security, amount) {
        var multiplier = 1;

        if (typeof security != "undefined" && security != null && typeof security.size_multiplier != "undefined" && security.size_multiplier != 0) {
            multiplier = security.size_multiplier;
        }

        return amount / multiplier;
    }

    this.recalculate = function(security) {
        security.shortName = this.getShortName(security);
        security.fullName = this.getFullName(security);
    }

} //end pilot security utils

var PilotUtils = new _PilotUtils();
var PilotSecurityUtils = new _PilotSecurityUtils();
var PilotMarketDataUtils = new _PilotMarketDataUtils();


if (!String.prototype.includes) {
    String.prototype.includes = function(search, start) {
        'use strict';
        if (typeof start !== 'number') {
            start = 0;
        }

        if (start + search.length > this.length) {
            return false;
        } else {
            return this.indexOf(search, start) !== -1;
        }
    };
}